﻿Imports System.IO
Imports System.IO.IsolatedStorage

''' <summary>
''' The screen manager is a component which manages one or more GameScreen
''' instances. It maintains a stack of screens, calls their Update and Draw
''' methods at the appropriate times, and automatically routes input to the
''' topmost active screen.
''' </summary>
Public Class ScreenManager
	Inherits DrawableGameComponent
#Region "Fields"

	Dim screens As List(Of GameScreen) = New List(Of GameScreen)()
	Dim screensToUpdate As List(Of GameScreen) = New List(Of GameScreen)()

	Dim input As InputState = New InputState()

	Dim _spriteBatch As SpriteBatch
	Dim _font As SpriteFont
	Dim _blankTexture As Texture2D

	Dim _isInitialized As Boolean

	Dim _traceEnabled As Boolean

#End Region

#Region "Properties"


	''' <summary>
	''' A default SpriteBatch shared by all the screens. This saves
	''' each screen having to bother creating their own local instance.
	''' </summary>
	Public ReadOnly Property SpriteBatch As SpriteBatch
		Get
			Return _spriteBatch
		End Get
	End Property


	''' <summary>
	''' A default font shared by all the screens. This saves
	''' each screen having to bother loading their own local copy.
	''' </summary>
	Public ReadOnly Property Font As SpriteFont
		Get
			Return _font
		End Get
	End Property

	''' <summary>
	''' If true, the manager prints out a list of all the screens
	''' each time it is updated. This can be useful for making sure
	''' everything is being added and removed at the right times.
	''' </summary>
	Public Property TraceEnabled As Boolean
		Get
			Return _traceEnabled
		End Get
		Set(value As Boolean)
			_traceEnabled = value
		End Set
	End Property

#End Region

#Region "Initialization"


	''' <summary>
	''' Constructs a new screen manager component.
	''' </summary>
	Public Sub New(Game As Game)
		MyBase.New(Game)
		' we must set EnabledGestures before we can query for them, but
		' we don't assume the game wants to read them.
		TouchPanel.EnabledGestures = GestureType.None
	End Sub


	''' <summary>
	''' Initializes the screen manager component.
	''' </summary>
	Public Overrides Sub Initialize()
		MyBase.Initialize()

		_isInitialized = True
	End Sub


	''' <summary>
	''' Load your graphics content.
	''' </summary>
	Protected Overrides Sub LoadContent()

		' Load content belonging to the screen manager.
		Dim Content As ContentManager = Game.Content

		_spriteBatch = New SpriteBatch(GraphicsDevice)
		_font = Content.Load(Of SpriteFont)("Textures/Interface/Fonts/menufont")
		_blankTexture = Content.Load(Of Texture2D)("blank")

		' Tell each of the screens to load their content.
		For Each screen As GameScreen In screens
			screen.LoadContent()
		Next
	End Sub


	''' <summary>
	''' Unload your graphics content.
	''' </summary>
	Protected Overrides Sub UnloadContent()

		' Tell each of the screens to unload their content.
		For Each screen As GameScreen In screens

			screen.UnloadContent()
		Next
	End Sub


#End Region

#Region "Update and Draw"


	''' <summary>
	''' Allows each screen to run logic.
	''' </summary>
	Public Overrides Sub Update(GameTime As GameTime)
		' Read the keyboard and gamepad.
		input.Update()

		' Make a copy of the master screen list, to avoid confusion if
		' the process of updating one screen adds or removes others.
		screensToUpdate.Clear()

		For Each screen As GameScreen In screens : screensToUpdate.Add(screen) : Next
		Dim otherScreenHasFocus As Boolean = Not (Game.IsActive)
		Dim coveredByOtherScreen As Boolean = False

		' Loop as long as there are screens waiting to be updated.
		While (screensToUpdate.Count > 0)

			' Pop the topmost screen off the waiting list.
			Dim screen As GameScreen = screensToUpdate(screensToUpdate.Count - 1)

			screensToUpdate.RemoveAt(screensToUpdate.Count - 1)

			' Update the screen.
			screen.Update(GameTime, otherScreenHasFocus, coveredByOtherScreen)

			If (screen.ScreenState = ScreenState.TransitionOn Or
			 screen.ScreenState = ScreenState.Active) Then

				' If this is the first active screen we came across,
				' give it a chance to handle input.
				If Not (otherScreenHasFocus) Then

					screen.HandleInput(input)

					otherScreenHasFocus = True
				End If

				' If this is an active non-popup, inform any subsequent
				' screens that they are covered by it.
				If Not (screen.IsPopup) Then _
				 coveredByOtherScreen = True
			End If

		End While
		' Print debug trace?
		If (TraceEnabled) Then _
		 TraceScreens()
	End Sub


	''' <summary>
	''' Prints a list of all the screens, for debugging.
	''' </summary>
	Sub TraceScreens()
		Dim screenNames As List(Of String) = New List(Of String)()

		For Each screen As GameScreen In screens
			screenNames.Add(screen.GetType().Name) : Next

		Debug.WriteLine(String.Join(", ", screenNames.ToArray()))
	End Sub


	''' <summary>
	''' Tells each screen to draw itself.
	''' </summary>
	Public Overrides Sub Draw(GameTime As GameTime)

		For Each screen As GameScreen In screens

			If (screen.ScreenState = ScreenState.Hidden) Then _
			 Continue For

			screen.Draw(GameTime)
		Next

	End Sub

#End Region

#Region "Public Methods"


	''' <summary>
	''' Adds a new screen to the screen manager.
	''' </summary>
	Public Sub AddScreen(screen As GameScreen, controllingPlayer As PlayerIndex?)
		screen.ControllingPlayer = controllingPlayer
		screen.ScreenManager = Me
		screen.IsExiting = False

		' If we have a graphics device, tell the screen to load content.
		If (_isInitialized) Then
			screen.LoadContent()
		End If
		screens.Add(screen)

		' update the TouchPanel to respond to gestures this screen is interested in
		TouchPanel.EnabledGestures = screen.EnabledGestures
	End Sub

	''' <summary>
	''' Removes a screen from the screen manager. You should normally
	''' use GameScreen.ExitScreen instead of calling this directly, so
	''' the screen can gradually transition off rather than just being
	''' instantly removed.
	''' </summary>
	Public Sub RemoveScreen(screen As GameScreen)

		' If we have a graphics device, tell the screen to unload content.
		If (_isInitialized) Then _
		 screen.UnloadContent()


		screens.Remove(screen)
		screensToUpdate.Remove(screen)

		' if there is a screen still in the manager, update TouchPanel
		' to respond to gestures that screen is interested in.
		If (screens.Count > 0) Then _
		 TouchPanel.EnabledGestures = screens(screens.Count - 1).EnabledGestures

	End Sub


	''' <summary>
	''' Expose an array holding all the screens. We return a copy rather
	''' than the real master list, because screens should only ever be added
	''' or removed using the AddScreen and RemoveScreen methods.
	''' </summary>
	Public Function GetScreens() As GameScreen()
		Return screens.ToArray()
	End Function

	''' <summary>
	''' Helper draws a translucent black fullscreen sprite, used for fading
	''' screens in and out, and for darkening the background behind popups.
	''' </summary>
	Public Sub FadeBackBufferToBlack(alpha!)

		Dim Viewport As Viewport = GraphicsDevice.Viewport

		SpriteBatch.Begin()

		SpriteBatch.Draw(_blankTexture, _
		  New Rectangle(0, 0, Viewport.Width, Viewport.Height), _
		  Color.Black * alpha)

		SpriteBatch.End()

	End Sub
	''' <summary>
	''' Informs the screen manager to serialize its state to disk.
	''' </summary>
	Public Sub SerializeState()
		' open up isolated storage
		Using storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication()
			' if our screen manager directory already exists, delete the contents
			If (storage.DirectoryExists("ScreenManager")) Then
				DeleteState(storage)
				' otherwise just create the directory
			Else
				storage.CreateDirectory("ScreenManager")
			End If

			' create a file we'll use to store the list of screens in the stack
			Using stream As IsolatedStorageFileStream = storage.CreateFile("ScreenManager\\ScreenList.dat")
				Using writer As BinaryWriter = New BinaryWriter(stream)
					' write out the full name of all the types in our stack so we can
					' recreate them if needed.
					For Each screen As GameScreen In screens
						If (screen.IsSerializable) Then _
						 writer.Write(screen.GetType().AssemblyQualifiedName)
					Next
				End Using
			End Using

			' now we create a new file stream for each screen so it can save its state
			' if it needs to. we name each file "ScreenX.dat" where X is the index of
			' the screen in the stack, to ensure the files are uniquely named
			Dim screenIndex% = 0
			For Each screen As GameScreen In screens
				If (screen.IsSerializable) Then
					Dim fileName$ = String.Format("ScreenManager\\Screen{0}.dat", screenIndex)

					' open up the stream and let the screen serialize whatever state it wants
					Using stream As IsolatedStorageFileStream = storage.CreateFile(fileName)
						screen.Serialize(stream)
					End Using

					screenIndex += 1
				End If
			Next
		End Using
	End Sub
	Public Function DeserializeState() As Boolean
		' open up isolated storage
		Using storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication()
			' see if our saved state directory exists
			If (storage.DirectoryExists("ScreenManager")) Then

				Try
					' see if we have a screen list
					If (storage.FileExists("ScreenManager\\ScreenList.dat")) Then
						' load the list of screen types
						Using stream As IsolatedStorageFileStream = storage.OpenFile("ScreenManager\\ScreenList.dat", FileMode.Open, _
						 FileAccess.Read)
							Using reader As BinaryReader = New BinaryReader(stream)
								While (reader.BaseStream.Position < reader.BaseStream.Length)
									' read a line from our file
									Dim line$ = reader.ReadString()

									' if it isn't blank, we can create a screen from it
									If Not (String.IsNullOrEmpty(line)) Then
										Dim screenType As Type = Type.GetType(line)
										Dim screen As GameScreen = TryCast(Activator.CreateInstance(screenType), GameScreen)
										AddScreen(screen, PlayerIndex.One)
									End If
								End While
							End Using
						End Using
					End If

					' next we give each screen a chance to deserialize from the disk
					For i% = 0 To screens.Count - 1
						Dim filename$ = String.Format("ScreenManager\\Screen{0}.dat", i)
						Using stream As IsolatedStorageFileStream = storage.OpenFile(filename, FileMode.Open, _
						 FileAccess.Read)
							screens(i).Deserialize(stream)
						End Using
					Next
					Return True
				Catch e As Exception

					' if an exception was thrown while reading, odds are we cannot recover
					' from the saved state, so we will delete it so the game can correctly
					' launch.
					DeleteState(storage)
				End Try
			End If
		End Using
		Return False
	End Function
	''' <summary>
	''' Deletes the saved state files from isolated storage.
	''' </summary>
	Private Sub DeleteState(storage As IsolatedStorageFile)

		' get all of the files in the directory and delete them
		Dim files$() = storage.GetFileNames("ScreenManager\\*")
		For Each file$ In files

			storage.DeleteFile(Path.Combine("ScreenManager", file))
		Next
	End Sub

#End Region
End Class